package edu.scau.divineplace.config.global.log;

import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @Author: x1aolone
 * @Date: 2020/8/22 20:10
 */
@Aspect
@Component
@Slf4j
public class LogAspect {

    private final String LOG_BEGIN = "==================== begin ====================";
    private final String LOG_END = "====================  end  ====================\n";


    /**
     * 针对使用@WebLog注解的类或方法
     * @annotation针对使用在方法上的注解
     * @within针对使用在类上的注解
     */
    @Pointcut("@within(edu.scau.divineplace.config.global.log.WebLog) || " +
            "@annotation(edu.scau.divineplace.config.global.log.WebLog)")
    public void webLog(){}

    /**
     * 传入byte为单位的size，转换为MB/KB
     * @param size
     * @return
     */
    public String getFileSize (int size) {
        // 单位为KB
        final int K = 1024;
        // 1024 * 1024
        final int M = 1048576;
        int result;
        if ((result = size / M) > 0) {
            return result + "MB";
        } else if ((result = size / K) > 0) {
            return result + "KB";
        } else {
            return size + "B";
        }
    }

    public Map<String, Object> getMultipartFileValue(MultipartFile file) {
        Map<String, Object> values = new HashMap<>();
        values.put("contentType", file.getContentType());
        values.put("originalFilename", file.getOriginalFilename());
        try {
            int size = file.getBytes().length;
            values.put("size", getFileSize(size));
        } catch (IOException e) {
            values.put("size", "unknown");
        }
        // 直接返回String的话，会导致JSONObject进行解析的时候，给字符串里面的"转换成\"
        return values;
    }

    public String getArgsJson(String[] argNames, Object[] argValues) {
        JSONObject args = new JSONObject();
        int length = Math.max(argValues.length, argNames.length);
        for (int i = 0; i < length; i++) {
            String argName = argNames[i];
            Object argValue = argValues[i];
            if (argValue instanceof MultipartFile) {
                argValue = getMultipartFileValue((MultipartFile) argValue);
            }
            args.put(argName, argValue);
        }
        return args.toJSONString();
    }

    public WebLog getAnnotation (JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        WebLog methodAnnotation = method.getAnnotation(WebLog.class);
        WebLog classAnnotation = method.getDeclaringClass().getAnnotation(WebLog.class);
        // 优先处理method上的参数, method上没有注解就用class上的注解
        return getAnnotation(methodAnnotation, classAnnotation);
    }

    public WebLog getAnnotation (WebLog... weblogs) {
        // 按顺序作为优先级处理
        for(WebLog annotation : weblogs) {
            if (annotation != null) {
                return annotation;
            }
        }
        return null;
    }

    @Before("webLog()")
    public void before (JoinPoint joinPoint) {
        log.info(LOG_BEGIN);
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        Method method = signature.getMethod();
        WebLog methodAnnotation = method.getAnnotation(WebLog.class);
        WebLog classAnnotation = method.getDeclaringClass().getAnnotation(WebLog.class);
        List<WebLog> descriptions = Arrays.asList(methodAnnotation, classAnnotation);
        descriptions.forEach(x -> {
            if (x != null) {
                log.info("description: {}", x.value());
            }
        });
        // 优先处理method上的参数, method上没有注解就用class上的注解
        boolean printArgs = getAnnotation(methodAnnotation, classAnnotation).printArgs();
        if (printArgs) {
            log.info("args: {}", getArgsJson(signature.getParameterNames(), joinPoint.getArgs()));
        } else {
            log.info("args: you have chosen not print args");
        }
        log.info("signature: {}", signature);

        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        log.info("URI: {}", request.getRequestURI());
        log.info("request type: {}", request.getMethod());
        log.info("requester IP: {}", request.getRemoteAddr());
    }

    @Around("webLog()")
    public Object around (ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        try {
            Object result = proceedingJoinPoint.proceed();
            WebLog webLog = getAnnotation(proceedingJoinPoint);
            if (webLog.printResult()) {
                log.info("result: {}", result);
            } else {
                log.info("result: you have chosen not print result");
            }
            return result;
        } catch (Throwable e) {
            // 让全局异常处理来处理这个异常
            log.error("webLog process error msg: {}", e.getMessage());
            throw e;
        }
    }

    @After("webLog()")
    public void after () {
        // 无论around执行具体方法会不会有异常，after都会在around之后执行
        log.info(LOG_END);
    }

}